﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Socium.Domain.Collections {
	public static class EnumerableExtensions {
		public static void Iterate<T>(this IEnumerable<T> enumerable, Action<T> action) {
			foreach (var t in enumerable) {
				action(t);
			}
		}

		public static void IterateIndexed<T>(this IEnumerable<T> enumerable, Action<T, int> action) {
			var index = 0;
			foreach (var item in enumerable) {
				action(item, index);
				index++;
			}
		}

		public static CollectionResult<TTarget> Map<TSource, TTarget>(this IEnumerable<TSource> enumerable, Func<TSource, TTarget> mapper) {
		    ICollection<TTarget> collection = new LinkedList<TTarget>();
			collection.AddRange(enumerable.MapLazy(mapper));

			return new CollectionResult<TTarget>(collection);
		}

		public static IEnumerable<TTarget> MapLazy<TSource, TTarget>(this IEnumerable<TSource> enumerable, Func<TSource, TTarget> mapper) {
			return enumerable.Select(mapper);
		}

		public static CollectionResult<TTarget> MapIndexed<TSource, TTarget>(this IEnumerable<TSource> enumerable, Func<TSource, int, TTarget> mapper) {
			ICollection<TTarget> result = new LinkedList<TTarget>();
			result.AddRange(enumerable.MapIndexedLazy(mapper));
			return new CollectionResult<TTarget>(result);
		}

		public static IEnumerable<TTarget> MapIndexedLazy<TSource, TTarget>(this IEnumerable<TSource> enumerable, Func<TSource, int, TTarget> mapper) {
			return enumerable.Select(mapper);
		}

		public static TTarget Reduce<TSource, TTarget>(this IEnumerable<TSource> enumerable, Func<TSource, TTarget> init, Func<TSource, TTarget, TTarget> func) {
			var e = enumerable.GetEnumerator();
			if (!e.MoveNext()) {
				throw new ArgumentException();
			}

			var acc = init(e.Current);
			while (e.MoveNext()) {
				acc = func(e.Current, acc);
			}

			return acc;
		}

		public static CollectionResult<T> Filter<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate) {
			ICollection<T> retval = new LinkedList<T>();
			retval.AddRange(enumerable.Where(predicate));

			return new CollectionResult<T>(retval);
		}

		public static IEnumerable<T> FilterLazy<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate) {
			return enumerable.Where(predicate);
		} 

		public static void Iterate2<T1, T2>(this IEnumerable<T1> enumerable1, IEnumerable<T2> enumerable2, Action<T1, T2> iterator) {
			var e1 = enumerable1.GetEnumerator();
			var e2 = enumerable2.GetEnumerator();

			while (e1.MoveNext() && e2.MoveNext()) {
				iterator(e1.Current, e2.Current);
			}
		}

		public static bool IsEmpty<T>(this IEnumerable<T> enumerable) {
			using (var enumerator = enumerable.GetEnumerator()) {
				return !enumerator.MoveNext();
			}
		}

		public static T Find<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate, T @default) {
			foreach (var item in enumerable.FilterLazy(predicate)) {
				return item;
			}

			return @default;
		}

		public static int Count<T>(this IEnumerable<T> enumerable) {
			return Enumerable.Count(enumerable);
		}

		public static IDictionary<TKey, TValue> MakeDictionary<TKey, TValue>(this IEnumerable<TValue> enumerable, Func<TValue, TKey> key_selector) {
			var result = new Dictionary<TKey, TValue>();
			result.AddRange(enumerable.MapLazy(value => value.MakePair(key_selector(value))));

			return result;
		}

		public static IEnumerable<T> Traverse<T>(this IEnumerable<T> enumerable, Func<T, IEnumerable<T>> selector) {
			foreach (var item in enumerable) {
				yield return item;

				foreach (var child in Traverse(selector(item), selector)) {
					yield return child;
				}
			}
		}

		public static string Join(this System.Collections.IEnumerable values, string separator) {
			var builder = new System.Text.StringBuilder();
			var first = true;

			foreach (var value in values) {
				if (value == null) {
					continue;
				}

				var string_value = value.ToString();
				if (string.IsNullOrEmpty(string_value)) {
					continue;
				}

				if (first) {
					first = false;
				}
				else {
					builder.Append(separator);
				}

				builder.Append(string_value);
			}

			return builder.ToString();
		}
	}
}
